/*
Module : FTPTransferer.CPP
Purpose: Defines the implementation for an MFC class which provides a wrapping for downloading HTTP requests in a 
         synchronous manner via Wininet
Created: PJN / 15-07-2006
History: PJN / 13-04-2007 1. Updated copyright details.
                          2. Following feedback from Philippe MARINUTTI, it looks like you just cannot do
                          FTP resumes using Wininet. For more information on the issues involved, please see
                          http://groups.google.com/group/microsoft.public.inetsdk.programming.wininet/browse_thread/thread/b43094df4de38a35/376b4c14acdb9be6?lnk=st&q=FtpCommand+REST&rnum=9&hl=en#376b4c14acdb9be6,
                          http://groups.google.com/group/microsoft.public.inetsdk.programming.wininet/browse_thread/thread/229a31ebda334098/4facc1ebcc9a7c2e?lnk=st&q=FtpCommand+REST&rnum=6&hl=en#4facc1ebcc9a7c2e,
                          http://groups.google.com/group/microsoft.public.inetsdk.programming.wininet/browse_thread/thread/5f1a1d5c92e6548d/1dfcc8fb46c6c721?lnk=st&q=FtpCommand+REST&rnum=10&hl=en#1dfcc8fb46c6c721 & 
                          http://groups.google.com/group/microsoft.public.inetsdk.programming.wininet/browse_thread/thread/31a840505f3b45bc/8c1732d682ba4f7b?lnk=st&q=FtpCommand+REST&rnum=1&hl=en#8c1732d682ba4f7b.
                          As such I have removed support for resumes (CFTPTransferer::m_dwStartPos) from the code.
                          If you would like to support FTP resumes, then you will need to look to commercial alternatives such as 
                          SmartFTP (http://www.smartftp.com/ftplib/), Xceed FTP Library (http://xceed.com/FTP_ActiveX_Intro.html),
                          or FTP Client Engine for C/C++ (http://www.marshallsoft.com/fce4c.htm).
         PJN / 30-12-2007 1. Updated the code to remove VC 6 style appwizard comments.
                          2. Updated the sample apps to clean compile on VC 2008.
                          3.  CFTPTransferer::GetErrorMessage now uses the FORMAT_MESSAGE_IGNORE_INSERTS flag. For more information please see Raymond Chen's blog at 
                          http://blogs.msdn.com/oldnewthing/archive/2007/11/28/6564257.aspx. Thanks to Alexey Kuznetsov for reporting this issue.
                          4. CFTPTransferer::GetErrorMessage now uses Checked::tcsncpy_s if compiled using VC 2005 or later.
         PJN / 18-05-2008 1. Updated copyright details
                          2. Addition of AttachSession and DetachSession methods which allow the lifetime of the session
                          to be controlled independently of the lifetime of the CHttpDownloader instance. Thanks to 
                          Hans Dietrich for prompting this update
                          3. Fixed a spelling mistake in the IDS_FTPTRANSFER_RETRIEVING_FILE string resource
                          
Copyright (c) 2006 - 2008 by PJ Naughter (Web: www.naughter.com, Email: pjna@naughter.com)

All rights reserved.

Copyright / Usage Details:

You are allowed to include the source code in any product (commercial, shareware, freeware or otherwise) 
when your product is released in binary form. You are allowed to modify the source code in any way you want 
except you cannot modify the copyright details at the top of each module. If you want to distribute source 
code with your application, then you are only allowed to distribute versions released by the author. This is 
to maintain a single distribution point for the source code. 

All rights reserved.

*/


/////////////////////////////////  Includes  //////////////////////////////////

#include "stdafx.h"
#include "resource.h"
#include "FTPTransferer.h"
#ifndef _INC_MALLOC
#pragma message("To avoid this message, please put malloc.h in your your pre compiled header (normally stdafx.h)")
#include <malloc.h>
#endif


///////////////////////////////// Macros / Defines ////////////////////////////

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

//Link to the wininet lib file automatically
#pragma comment(lib, "Wininet.lib")


///////////////////////////////// Implementation //////////////////////////////

CFTPTransferer::CFTPTransferer() : m_bDownload(TRUE),
                                   m_nPort(INTERNET_DEFAULT_FTP_PORT),     
                                   m_bBinary(TRUE),
                                   m_bPasv(FALSE),
                                   m_dbLimit(0),
                                   m_dwConnectionTimeout(0),
                                   m_ConnectionType(UsePreConfig),
                                   m_dwReadBufferSize(1024),
                                   m_hInternetSession(NULL),
                                   m_hFTPConnection(NULL),
                                   m_hFTPFile(NULL),
                                   m_lAbort(0),
                                   m_dwTempError(0)
{
}

CFTPTransferer::~CFTPTransferer()
{
  ReleaseHandles();
}

void CFTPTransferer::AttachSession(HINTERNET hInternetSession)
{
  if (m_hInternetSession)
    ::InternetCloseHandle(m_hInternetSession);
  m_hInternetSession = hInternetSession;
}

HINTERNET CFTPTransferer::DetachSession()
{
  HINTERNET hTempSession = m_hInternetSession;
  m_hInternetSession = NULL;
  return hTempSession;
} 

void CFTPTransferer::ReleaseHandles()
{
  //Serialize access to the handles
  CSingleLock sl(&m_csHandles, TRUE);
  
  //Free up the internet handles we may be using
  if (m_hFTPFile)
  {
    ::InternetCloseHandle(m_hFTPFile);
    m_hFTPFile = NULL;
  }
  if (m_hFTPConnection)
  {
    ::InternetCloseHandle(m_hFTPConnection);
    m_hFTPConnection = NULL;
  }
  if (m_hInternetSession)
  {
    ::InternetCloseHandle(m_hInternetSession);
    m_hInternetSession = NULL;
  }
}

BOOL CFTPTransferer::DeleteDownloadedFile()
{
  BOOL bSuccess = ::DeleteFile(m_sLocalFile);
  if (!bSuccess)
    TRACE(_T("CFTPTransferer::DeleteDownloadedFile, Failed to delete failed downloaded file, Error:%d\n"), ::GetLastError());
  return bSuccess;
}

BOOL CFTPTransferer::Transfer()
{
  //Remember if we have the file to download into before we do the download
  BOOL bDownloadFileAlreadyExists = (GetFileAttributes(m_sLocalFile) != INVALID_FILE_ATTRIBUTES);

  //Call the internal version which does the heavy lifting
  BOOL bSuccess = _Transfer();

  //Free up the internet handles now that we are finished with them
  ReleaseHandles();
  
  //Close the local file
  try
  {
    m_LocalFile.Close();
  }
  catch(CFileException* pEx)
  {
    pEx->Delete();
  }
    
  //Remove any downloaded file which did not come down successfully (when it did not exist before we did the download) 
  if (m_bDownload && !bSuccess && !bDownloadFileAlreadyExists && GetFileAttributes(m_sLocalFile))
    DeleteDownloadedFile();
  
  return bSuccess;
}

BOOL CFTPTransferer::CreateSession()
{
  //Serialize access to the handles
  CSingleLock sl(&m_csHandles, TRUE);

  //Create the Internet session handle
  if (m_hInternetSession == NULL)
  {
    switch (m_ConnectionType)
    {
      case UsePreConfig:
      {
        m_hInternetSession = ::InternetOpen(m_sUserAgent.GetLength() ? m_sUserAgent : AfxGetAppName(), INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);    
        break;
      }
      case DirectToInternet:
      {
        m_hInternetSession = ::InternetOpen(m_sUserAgent.GetLength() ? m_sUserAgent : AfxGetAppName(), INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);    
        break;
      }
      case UseProxy:
      {
        ASSERT(m_sProxyServer.GetLength()); //You need to give me a proxy Server
        m_hInternetSession = ::InternetOpen(m_sUserAgent.GetLength() ? m_sUserAgent : AfxGetAppName(), INTERNET_OPEN_TYPE_PROXY, m_sProxyServer, NULL, 0);    
        break;
      }
      default:  
      {
        ASSERT(FALSE);
        break;
      }
    }
  }

  if (m_hInternetSession == NULL)
  {
    DWORD dwError = GetLastError();
    TRACE(_T("CFTPTransferer::CreateSession, Failed in call to InternetOpen, Error:%d\n"), dwError);
    HandleError(IDS_FTPTRANSFER_GENERIC_ERROR, dwError);
    return FALSE;
  }

  //Setup the status callback function
  if (::InternetSetStatusCallback(m_hInternetSession, _OnWininetStatusCallBack) == INTERNET_INVALID_STATUS_CALLBACK)
  {
    DWORD dwError = GetLastError();
    TRACE(_T("CFTPTransferer::Download, Failed in call to InternetSetStatusCallback, Error:%d\n"), dwError);
    HandleError(IDS_FTPTRANSFER_GENERIC_ERROR, dwError);
    return FALSE;
  }

  return TRUE;
}

BOOL CFTPTransferer::CreateConnection()
{
  //Serialize access to the handles
  CSingleLock sl(&m_csHandles, TRUE);

  //Make the connection to the HTTP server          
  ASSERT(m_hFTPConnection == NULL);

  //Should we try to call InternetConnect synchronously or use a worker thread to make the connection on our behalf 
  if (m_dwConnectionTimeout == 0)
    MakeConnection();
  else
  {
    //Spin of the thread which does the connection for us. Note we use a worker thread so that we avoid
    //the blocking behaviour which FTP wininet suffers from and which cannot be easily fixed.
    CWinThread* pConnectionThread = AfxBeginThread(_MakeConnectionThread, this, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
    if (pConnectionThread == NULL)
    {
      DWORD dwError = GetLastError();
      TRACE(_T("CFTPTransferer::CreateConnection, Failed in create the connection thread, Error:%d\n"), dwError);
      HandleError(IDS_FTPTRANSFER_FAIL_CONNECT_SERVER, dwError);
      return FALSE;
    }

    //We're looking after its memory
    pConnectionThread->m_bAutoDelete = FALSE;

    //Everything ok, Resume the thread
    pConnectionThread->ResumeThread();

    //Wait for the thread to return or a timeout occurs
    if (WaitForSingleObject(pConnectionThread->m_hThread, m_dwConnectionTimeout) == WAIT_TIMEOUT)
    {
      //Close the wininet session handle (which will cause the worker thread to return from its blocking call which 
      //most likely will be the call to InternetConnect)
      ::InternetCloseHandle(m_hInternetSession);
      m_hInternetSession = NULL;
    
      //Wait until the worker thread exits, should be quick since we have just closed the session handle above!
      WaitForSingleObject(pConnectionThread->m_hThread, INFINITE);
      delete pConnectionThread;

      TRACE(_T("CFTPTransferer::CreateConnection, Failed in connect to the FTP server in a timely manner\n"));
      HandleError(IDS_FTPTRANSFER_FAIL_CONNECT_SERVER, ERROR_TIMEOUT);    

      return FALSE;
    }

    //If we got here
    delete pConnectionThread;
  }

  //Check to see if the connection was successful
  if (m_hFTPConnection == NULL)
  {
    TRACE(_T("CFTPTransferer::CreateConnection, Failed in call to InternetConnect, Error:%d\n"), m_dwTempError);
    HandleError(IDS_FTPTRANSFER_FAIL_CONNECT_SERVER, m_dwTempError);
    return FALSE;
  }

  return TRUE;
}

BOOL CFTPTransferer::CreateRequest()
{
  //Serialize access to the handles
  CSingleLock sl(&m_csHandles, TRUE);

  //Open the FTP file
  ASSERT(m_hFTPFile == NULL);
  m_hFTPFile = FtpOpenFile(m_hFTPConnection, m_sRemoteFile, m_bDownload ? GENERIC_READ : GENERIC_WRITE, m_bBinary ? FTP_TRANSFER_TYPE_BINARY | INTERNET_FLAG_RELOAD | INTERNET_FLAG_DONT_CACHE : 
                           FTP_TRANSFER_TYPE_ASCII | INTERNET_FLAG_RELOAD | INTERNET_FLAG_DONT_CACHE, reinterpret_cast<DWORD_PTR>(this));
  if (m_hFTPFile == NULL)
  {
    DWORD dwLastError = ::GetLastError();
    TRACE(_T("CFTPTransferer::_Transfer, Failed in call to FtpOpenFile, Error:%d\n"), dwLastError);
    HandleError(IDS_FTPTRANSFER_FAIL_OPEN_FILE, dwLastError);
    return FALSE;
  }
  
  //Update the status to say that we are now uploading / downloading the file
  if (m_bDownload)
    SetStatus(IDS_FTPTRANSFER_RETRIEVING_FILE);
  else
    SetStatus(IDS_FTPTRANSFER_UPLOADING_FILE);

  return TRUE;
}

BOOL CFTPTransferer::OnTransferData(const BYTE* pbyData, DWORD dwSize)
{
  if (m_bDownload)
  {
    //Write the data to file
    try
    {
      m_LocalFile.Write(pbyData, dwSize);
    }
    catch(CFileException* pEx)
    {
      TRACE(_T("CFTPTransferer::OnTransferData, An exception occured while writing to the download file\n"));
      HandleError(IDS_FTPTRANSFER_ERROR_WRITEFILE, pEx->m_lOsError);
      pEx->Delete();
      return FALSE;
    }
  }
  else
  {
    ASSERT(m_hFTPFile);
    DWORD dwBytesWritten = 0;
    if (!::InternetWriteFile(m_hFTPFile, pbyData, dwSize, &dwBytesWritten))
    {
      DWORD dwLastError = ::GetLastError();
      TRACE(_T("CFTPTransferDlg::TransferThread, Failed in call to InternetWriteFile, Error:%d\n"), dwLastError);
      HandleError(IDS_FTPTRANSFER_ERROR_WRITEFILE, dwLastError);
      return FALSE;
    }
  }

  //Continue the download if we are not aborting
  return !Abort();
}

BOOL CFTPTransferer::GetRemoteFileLength(ULONGLONG& nFileSize)
{
  //What will be the return value (assume the worst)
  BOOL bSuccess = FALSE;

  WIN32_FIND_DATA wfd;
  HINTERNET hFind = ::FtpFindFirstFile(m_hFTPConnection, m_sRemoteFile, &wfd, INTERNET_FLAG_RELOAD | INTERNET_FLAG_DONT_CACHE, reinterpret_cast<DWORD_PTR>(this)); 
  if (hFind)
  {
    bSuccess = TRUE;

    //Set the file size output parameter
    nFileSize = wfd.nFileSizeLow + (static_cast<ULONGLONG>(wfd.nFileSizeHigh) << 32);

    //Destroy the enumeration handle now that we are finished with it
    InternetCloseHandle(hFind);
  }
  
  return bSuccess;
}

BOOL CFTPTransferer::_Transfer()
{
  //Make sure our wininet handles are empty
  ReleaseHandles();

  //Try and open the file we will downloading into / uploading from
  if (m_bDownload)
  {
    BOOL bDownloadFileExists = (GetFileAttributes(m_sLocalFile) != INVALID_FILE_ATTRIBUTES);
    CFileException ex;
    if (!m_LocalFile.Open(m_sLocalFile, bDownloadFileExists ? CFile::modeCreate | CFile::modeNoTruncate | CFile::modeWrite | CFile::shareDenyWrite :
                          CFile::modeCreate | CFile::modeWrite | CFile::shareDenyWrite, &ex))
    {
      TRACE(_T("CFTPTransferer::_Transfer, Failed to open the file to download into, Error:%d\n"), ex.m_lOsError);
      HandleError(IDS_FTPTRANSFER_FAIL_LOCALFILE_OPEN1, ex.m_lOsError);
      return FALSE;
    }

		//Seek to the correct start position
    try
	  {
      m_LocalFile.Seek(0, CFile::begin); 
      m_LocalFile.SetLength(0);
	  }
    catch(CFileException* pEx)                                         
    {
      TRACE(_T("CFTPTransferer::_Transfer, An exception occured while setting file pointer position, Error:%d\n"), pEx->m_lOsError);
      HandleError(IDS_FTPTRANSFER_FAIL_FILE_SEEK, pEx->m_lOsError);
		  pEx->Delete();
      return FALSE;
    }	
  }
  else
  {
    CFileException ex;
    if (!m_LocalFile.Open(m_sLocalFile, CFile::modeRead | CFile::shareDenyWrite, &ex))
    {
      TRACE(_T("CFTPTransferer::_Transfer, Failed to open the file to upload, Error:%d\n"), ex.m_lOsError);
      HandleError(IDS_FTPTRANSFER_FAIL_LOCALFILE_OPEN2, ex.m_lOsError);
      return FALSE;
    }
  }

  //Call the virtual functions which create the session, connection
  if (!CreateSession())
    return FALSE;
  if (!CreateConnection())
    return FALSE;

  //Get the length of the file on the FTP server
  ULONGLONG nFileSize = 0;
  BOOL bGotFileSize = FALSE;
  if (m_bDownload)
  {
    //Update the status control to reflect that we are getting the file information
    SetStatus(IDS_FTPTRANSFER_GETTING_FILE_INFORMATION);

    bGotFileSize = GetRemoteFileLength(nFileSize);
  } 
  else
  {
    bGotFileSize = TRUE;
    nFileSize = m_LocalFile.GetLength();
  }

  //Call the virtual function to open the FTP file
	if (!CreateRequest()) 
	  return FALSE;

  //Now do the actual read of the file
  DWORD dwStartTicks = ::GetTickCount();
  DWORD dwBytesRead = 0;
  BYTE* byReadBuf = static_cast<BYTE*>(_alloca(m_dwReadBufferSize));
  ULONGLONG nTotalBytesRead = 0;

  do
  {
    if (m_bDownload)
    {
      //Read from the remote file
      if (!::InternetReadFile(m_hFTPFile, byReadBuf, m_dwReadBufferSize, &dwBytesRead))
      {
        DWORD dwLastError = ::GetLastError();
        TRACE(_T("CFTPTransferer::_Transfer, Failed in call to InternetReadFile, Error:%d\n"), dwLastError);
        HandleError(IDS_FTPTRANSFER_ERROR_READFILE, dwLastError);
        return FALSE;
      }
    }
    else
    {
      //Read the data from the local file
      try
      {
        dwBytesRead = m_LocalFile.Read(byReadBuf, m_dwReadBufferSize);
      }
      catch(CFileException* pEx)
      {
        TRACE(_T("CFTPTransferer::_Transfer, An exception occured while reading the local file, Error:%d\n"), pEx->m_lOsError);
        HandleError(IDS_FTPTRANSFER_ERROR_READFILE, pEx->m_lOsError);
        pEx->Delete();
        return FALSE;
      }
    }

    if (dwBytesRead)
    {
      //Call the virtual function which handles the transfer
      if (!OnTransferData(byReadBuf, dwBytesRead))
        return FALSE;
    
      //Increment the total number of bytes read
      nTotalBytesRead += dwBytesRead;  

      // For bandwidth throtling
	    if (m_dbLimit > 0) 
      {
        double t = static_cast<double>(GetTickCount() - dwStartTicks);
        double q = static_cast<double>(static_cast<LONGLONG>(nTotalBytesRead)) / t;
       
        if (q > m_dbLimit)	 
          Sleep(static_cast<DWORD>(((q*t)/m_dbLimit) - t));
	    }

      //Call the virtual function which reports progress as the download is occuring
      OnProgress(nTotalBytesRead, bGotFileSize, nFileSize);
    }
  }
  while (dwBytesRead && !Abort());

  //We're finished
  return TRUE;
}

void CFTPTransferer::OnProgress(ULONGLONG nTotalBytesRead, BOOL bGotMaxLength, ULONGLONG nTotalBytesToRead)
{
//To remove unreferrenced variable warnings in VC 2005
#ifndef _DEBUG
  nTotalBytesRead;
  nTotalBytesToRead;
#endif

  //Just trace out the progress, derived classes are free to implement something more useful
  if (bGotMaxLength)
    TRACE(_T("Transferred %I64u of %I64u bytes\n"), nTotalBytesRead, nTotalBytesToRead);
  else
    TRACE(_T("Transferred %I64u bytes\n"), nTotalBytesRead);
}

BOOL CFTPTransferer::Abort()
{
  volatile LONG lAbort = 0;
  InterlockedExchange(&lAbort, m_lAbort);
  return (lAbort != 0);
}

void CFTPTransferer::SetAbort()
{
  InterlockedExchange(&m_lAbort, TRUE);
  
  //Also release the Wininet handles, this should cause any blocking calls to Wininet to fail with errors
  ReleaseHandles();
}

void CFTPTransferer::HandleError(UINT nID, DWORD dwError)
{
  //Just trace out the error, derived classes are free to implement something more useful
  CString sError;
  if (dwError == ERROR_INTERNET_EXTENDED_ERROR)
  {
    DWORD dwInetError = 0;
    DWORD dwSize = 0;
    ::InternetGetLastResponseInfo(&dwInetError, NULL, &dwSize);
    TCHAR* pszResponse = new TCHAR[dwSize+1];
    ::InternetGetLastResponseInfo(&dwInetError, pszResponse, &dwSize);
    pszResponse[dwSize] = _T('\0');
    sError = pszResponse;
    delete [] pszResponse;
  }
  else
    sError = GetErrorMessage(dwError);
  CString sErrorMsg;
  AfxFormatString1(sErrorMsg, nID, sError);
  TRACE(_T("%s\n"), sErrorMsg.operator LPCTSTR());
}

void CFTPTransferer::SetStatus(UINT nID)
{
  //Just load up the string and pass to the other version of the function
  CString sStatus;
  sStatus.LoadString(nID);
  SetStatus(sStatus);
}

void CFTPTransferer::SetStatus(UINT nID, LPCTSTR lpsz1)
{
  //Just form out the string and pass to the other version of the function
  CString sStatus;
  AfxFormatString1(sStatus, nID, lpsz1);
  SetStatus(sStatus);
}

void CFTPTransferer::SetStatus(LPCTSTR pszStatus)
{
//To remove unreferrenced variable warnings in VC 2005
#ifndef _DEBUG
  pszStatus;
#endif

  //Just trace out the string, derived classes are free to implement something more useful
  TRACE(_T("CFTPTransferer::SetStatus, %s\n"), pszStatus);
}

CString CFTPTransferer::GetErrorMessage(DWORD dwError)
{
  //use the Wininet dll also to find error strings
  HMODULE hWininet = GetModuleHandle(_T("WININET.DLL"));

  CString rVal;
  LPTSTR pstrError = rVal.GetBuffer(4096);

	LPTSTR lpBuffer;
	DWORD dwReturn = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | 
	                               FORMAT_MESSAGE_FROM_HMODULE, hWininet, dwError, MAKELANGID(LANG_NEUTRAL, SUBLANG_SYS_DEFAULT), 
	                               reinterpret_cast<LPTSTR>(&lpBuffer), 0, NULL);
	if (dwReturn == 0)
		*pstrError = _T('\0');
	else
	{
	#if (_MSC_VER >= 1400)
	  Checked::tcsncpy_s(pstrError, 4096, lpBuffer, _TRUNCATE);
	#else
		lstrcpyn(pstrError, lpBuffer, 4096);
	#endif
		LocalFree(lpBuffer);
	}
  rVal.ReleaseBuffer();

	return rVal;
}

void CALLBACK CFTPTransferer::_OnWininetStatusCallBack(HINTERNET hInternet, DWORD_PTR dwContext, DWORD dwInternetStatus, 
                                                  LPVOID lpvStatusInformation, DWORD dwStatusInformationLength)
{
  //Convert from the SDK C world to the C++ world
  CFTPTransferer* pThis = reinterpret_cast<CFTPTransferer*>(dwContext);
  ASSERT(pThis);
  pThis->OnWininetStatusCallBack(hInternet, dwInternetStatus, lpvStatusInformation, dwStatusInformationLength);
}

void CFTPTransferer::OnWininetStatusCallBack(HINTERNET /*hInternet*/, DWORD /*dwInternetStatus*/, LPVOID /*lpvStatusInformation*/, DWORD /*dwStatusInformationLength*/)
{
  //The base class does nothing with this information, derived classes are free to implement something more useful
}

UINT CFTPTransferer::_MakeConnectionThread(LPVOID pParam)
{
  //Convert from the SDK world to the C++ world
  CFTPTransferer* pThis = reinterpret_cast<CFTPTransferer*>(pParam);
  ASSERT(pThis);
  
  return pThis->MakeConnection();
}

UINT CFTPTransferer::MakeConnection()
{
  //Validate our parameters
  ASSERT(m_hFTPConnection == NULL);
  ASSERT(m_sServer.GetLength());
  
  if (m_sUserName.GetLength())
    m_hFTPConnection = ::InternetConnect(m_hInternetSession, m_sServer, m_nPort, m_sUserName, 
                                         m_sPassword, INTERNET_SERVICE_FTP, m_bPasv ? INTERNET_FLAG_PASSIVE : 0, reinterpret_cast<DWORD_PTR>(this));
  else
    m_hFTPConnection = ::InternetConnect(m_hInternetSession, m_sServer, m_nPort, NULL, 
                                         NULL, INTERNET_SERVICE_FTP, m_bPasv ? INTERNET_FLAG_PASSIVE : 0, reinterpret_cast<DWORD_PTR>(this));
  if (m_hFTPConnection == NULL)
  {
    //Hive away this threads error code as we have use for it in the main thread
    m_dwTempError = ::GetLastError();
  }

  return m_hFTPConnection ? 0 : 1;
}
